/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools device manager
 */

#include "device_manager.h"

#include <cstdio>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fs.h>
#include <mntent.h>
#include <regex>
#include <string>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef _WIN32
#define O_BINARY 0
#endif

#include "hmfs_common.h"
#include "hmfs_io.h"
#include "hmfs_utils.h"
#include "hmfs_zoned.h"

namespace OHOS {
namespace Hmfs {
DeviceManager &DeviceManager::GetInstance()
{
    static DeviceManager instance(CmdParser::GetSingleton().GetCmdConfig());
    return instance;
}

int32_t DeviceManager::CreateDeviceInfoPart1(std::unique_ptr<DeviceInfo> &device,
    struct stat *statInfo, bool isMetaDevice)
{
    if (S_ISREG(statInfo->st_mode)) {
        device->sectorCount = statInfo->st_size / device->sectorSize;
    } else if (S_ISBLK(statInfo->st_mode)) {
#ifdef BLKSSZGET
        uint32_t sectorSize = 0;
        if (ioctl(device->fd, BLKSSZGET, &sectorSize) < 0) {
            HMFS_ERROR("Using the default sector size. errno: %d", errno);
        } else if (device->sectorSize < sectorSize) {
            device->sectorSize = sectorSize;
        }
#endif

#ifdef BLKGETSIZE64
        if (ioctl(device->fd, BLKGETSIZE64, &device->sectorCount) < 0) {
            HMFS_ERROR("Cannot get the device size. errno: %d", errno);
            return -1;
        }
#else
        if (ioctl(device->fd, BLKGETSIZE, &device->sectorCount) < 0) {
            HMFS_ERROR("Cannot get the device size. errno: %d", errno);
            return -1;
        }
#endif
        device->sectorCount /= device->sectorSize;
    } else {
        HMFS_ERROR("Volume type is not supported");
        return -1;
    }

    if ((!isMetaDevice) && (device->sectorSize != devices_[0]->sectorSize)) {
        HMFS_ERROR("Different sector sizes");
        return -1;
    }
    return 0;
}

int32_t DeviceManager::CreateDeviceInfoPart2(std::unique_ptr<DeviceInfo> &device)
{
    if (device->zonedModel != HMFS_ZONED_NONE) {
        if (HmfsZoned::GetInstance().HmfsGetZoneBlocks(device.get())) {
            HMFS_ERROR("Failed to get number of blocks per zone");
            return -1;
        }

        if (!HmfsCommon::GetInstance().IsPowerOf2(device->zoneSize)) {
            HMFS_INFO("zoned: zone size %" PRIu64 " (not a power of 2)", device->zoneSize);
        }

        if (HmfsZoned::GetInstance().HmfsCheckZones(device.get())) {
            HMFS_ERROR("Failed to check zone configuration");
            return -1;
        }
        HMFS_INFO("Host-%s zoned block device:", (device->zonedModel == HMFS_ZONED_HA) ? "aware" : "managed");
        HMFS_INFO("      %u zones, %" PRIu64 " zone size(bytes), %u randomly writeable zones",
            device->zoneCount, device->zoneSize, device->randomZonesCount);
        HMFS_INFO("      %zu blocks per zone", device->zoneBlocks);
    }

    if (cmdPara_.wantedSectorCount != std::numeric_limits<uint64_t>::max()) {
        HMFS_INFO("wanted sector count : %" PRIu64 ", wanted sector size : %" PRIu64 ".",
            cmdPara_.wantedSectorCount, cmdPara_.wantedSectorSize);
        if (cmdPara_.wantedSectorSize == std::numeric_limits<uint64_t>::max()) {
            cmdPara_.wantedSectorSize = device->sectorSize;
        } else if (device->sectorSize != cmdPara_.wantedSectorSize) {
            cmdPara_.wantedSectorCount *= cmdPara_.wantedSectorSize;
            cmdPara_.wantedSectorCount /= device->sectorSize;
        }
    }
    totalSectors_ += device->sectorCount;
    devices_.emplace_back(std::move(device));
    return 0;
}

int32_t DeviceManager::CreateDeviceInfo(std::string &path, bool isMetaDevice)
{
    auto device = std::make_unique<DeviceInfo>();
    device->path = path;
    device->isMetaDevice = isMetaDevice;

    char realPath[PATH_MAX];
    if (!realpath(path.c_str(), realPath)) {
        HMFS_ERROR("Failed to realpath the device, errno %d", errno);
        return -1;
    }

    struct stat statInfo;
    if (stat(realPath, &statInfo) < 0) {
        HMFS_ERROR("Failed to get the device stat, errno %d", errno);
        return -1;
    }

    if (S_ISBLK(statInfo.st_mode)) {
        if (HmfsZoned::GetInstance().HmfsGetZonedModel(device.get()) < 0) {
            return -1;
        }
    }

    int32_t flags = O_RDWR;
    if (S_ISBLK(statInfo.st_mode)) {
        flags |= O_EXCL;
    }

    device->fd = open(realPath, flags);
    if (device->fd < 0) {
        HMFS_ERROR("Failed to open the device, errno %d", errno);
        return -1;
    }
    if (CreateDeviceInfoPart1(device, &statInfo, isMetaDevice) < 0) {
        return -1;
    }
    if (CreateDeviceInfoPart2(device) < 0) {
        return -1;
    }
    return 0;
}

uint32_t DeviceManager::GetDeviceCount()
{
    return devices_.size();
}

DeviceInfo *DeviceManager::GetDeviceInfo(uint32_t deviceId)
{
    if (deviceId >= devices_.size()) {
        return nullptr;
    }
    return devices_[deviceId].get();
}

uint64_t DeviceManager::GetTotalSectors()
{
    return totalSectors_;
}

bool DeviceManager::CheckDeviceFormated()
{
    return false;
}

int32_t DeviceManager::TrimDevices()
{
    for (auto &device : devices_) {
        if (TrimDevice(device.get())) {
            HMFS_ERROR("Failed to trim whole device.");
            return -1;
        }
    }

    return 0;
}

int32_t DeviceManager::TrimDevice(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is null");
    int32_t fd = deviceInfo->fd;
    struct stat statInfo;
    if (fstat(fd, &statInfo) < 0) {
        HMFS_ERROR("Failed to get the device stat");
        return -1;
    }

    uint64_t range[2] = {0, deviceInfo->sectorCount * deviceInfo->sectorSize};

#if defined(WITH_BLKDISCARD) && defined(BLKDISCARD)
    HMFS_INFO("Discarding device");
    if (S_ISREG(statInfo.st_mode)) {
#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
        HMFS_DEBUG("range[1] : %" PRIu64 "", range[1]);
        if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, range[0], range[1]) < 0) {
            HMFS_INFO("fallocate(PUNCH_HOLE|KEEP_SIZE) is failed");
        }
#endif
        return 0;
    } else if (S_ISBLK(statInfo.st_mode)) {
        if (deviceInfo->zonedModel != HMFS_ZONED_NONE) {
            return HmfsZoned::GetInstance().HmfsResetZones(deviceInfo);
        }
#ifdef BLKSECDISCARD
        if (ioctl(fd, BLKSECDISCARD, &range) < 0) {
            HMFS_INFO("This device doesn't support BLKSECDISCARD");
        } else {
            HMFS_INFO("Secure Discarded %" PRIu64 " MB", HmfsBytesToMb(statInfo.st_size));
            return 0;
        }
#endif
        if (ioctl(fd, BLKDISCARD, &range) < 0) {
            HMFS_INFO("This device doesn't support BLKDISCARD");
        } else {
            HMFS_INFO("Discarded %" PRIu64 " MB", HmfsBytesToMb(range[1]));
        }
    } else {
        return -1;
    }
#endif
    return 0;
}

bool DeviceManager::IsDeviceMounted(std::string &path)
{
    if (IsMounted("/proc/mounts", path)) {
        return true;
    }

    if (IsMounted(MOUNTED, path)) {
        return true;
    }

    if (IsRootDevice(path)) {
        std::string rootPath = "/dev/root";
        if (IsMounted("/proc/mounts", rootPath)) {
            return true;
        }
    }

    char realPath[PATH_MAX];
    if (!realpath(path.c_str(), realPath)) {
        HMFS_DEBUG("Failed to realpath the device, errno %d", errno);
        return false;
    }

    struct stat statInfo;
    if (stat(realPath, &statInfo) == 0 && S_ISBLK(statInfo.st_mode)) {
        int32_t fd = open(realPath, O_RDONLY | O_EXCL);
        if (fd >= 0) {
            close(fd);
        } else if (errno == EBUSY) {
            return true;
        }
    }

    return false;
}

bool DeviceManager::IsMounted(const char *mountPoint, std::string &device)
{
    FILE *file = setmntent(mountPoint, "r");
    if (file == NULL) {
        return false;
    }

    struct mntent *mountEntry = nullptr;
    while ((mountEntry = getmntent(file)) != nullptr) {
        if (device == mountEntry->mnt_fsname) {
            break;
        }
    }
    endmntent(file);
    return (mountEntry != nullptr);
}

bool DeviceManager::IsRootDevice(std::string &path)
{
    struct stat statInfo;
    if (stat("/", &statInfo) == -1) {
        return false;
    }

    int32_t majorNum = major(statInfo.st_dev);
    int32_t minorNum = minor(statInfo.st_dev);
    std::string ueventPath = "/sys/dev/block/" + std::to_string(majorNum) + ":" + std::to_string(minorNum) + "/uevent";

    std::ifstream ifs(ueventPath);
    if (!ifs.is_open()) {
        return false;
    }
    std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());

    std::smatch match;
    std::regex pattern(R"(DEVNAME=([^\\n]+)\n)");
    if (std::regex_search(content, match, pattern)) {
        std::string rootDevice = "/dev/" + match[1].str();
        if (rootDevice == path) {
            return true;
        }
    }
    return false;
}

} // namespace Hmfs
} // namespace OHOS
